/*
Main javascript source for the UI
This contains pretty much everything!

(and in no particular order, so... enjoy!)
*/

// The window load hook
window.onload = function() {
	window.cal = $('cal');
	window.calHeight = 38;
	window.calWidth = 55;
	window.colourIdx = 0;
	
	// this hash stores the loaded timetable series
	window.items = new Hash();
	
	// this array stores loaded events for clash-checking
	window.evts = new Array();
	
	// optimisation options
	window.optim_options = null;
	window.optim_collection = null;
	
	var startedSession = false;
	
	// check for params
	if (window.location.hash) {
	    var params = window.location.hash.gsub("#", "?").toQueryParams();
	    if (params.session) {
	        _loadSession(params.session);
	        init();
	        startedSession = true;
	    }
    }
    
    if (!startedSession)
        startSession();
}

// Loads the timetable display and sets up events
// (this is only run once, after the server returns a new user session UUID)
function init()
{
	var cal = window.cal;
	var width = window.calWidth;
	var height = window.calHeight;
	
	window.cal.setStyle( {'width': width+'em', 'height': height+'em' });
	for (var day=0; day < 7; ++day) {
		for (var hour=0; hour <12; ++hour) {
			var e = $(document.createElement('div'));
			e.addClassName('timeblock');
			e.setStyle( { 
				'left': 3 + day*((width-3)/7)+'em', 
				'width': ((width-3)/7)+'em',
				'top': 2+hour*((height-2)/12)+'em',
				'height': ((height-2)/12)+'em',
				'display': 'none'
				} );
			window.cal.appendChild(e);
			e.appear();
		}
	}
	
	days = ['Sun', 'Mon', 'Tues', 'Wednes', 'Thurs', 'Fri', 'Satur'];
	for (var day=0; day < 7; ++day) {
		var he = $(document.createElement('div'));
		he.addClassName('h_heading');
		he.setStyle( {
			'left': 3 + day*((width-3)/7)+'em',
			'width': ((width-3)/7)+'em',
			'display': 'none'
			});
		he.update(days[day]+'day')
		cal.appendChild(he);
		he.appear();
	}
	
	hours = ['7am', '8am', '9am', '10am', '11am', '12pm', '1pm', '2pm', '3pm', '4pm', '5pm', '6pm', '7pm']
	for (var hour=0; hour <= 12; ++hour) {
		var he = $(document.createElement('div'));
		he.addClassName('v_heading');
		he.setStyle({
			'top': 2+hour*((height-2)/12)-0.5+'em',
			'display': 'none'
			});
		he.update(hours[hour]);
		cal.appendChild(he);
		he.appear();
	}
	
	Effect.SlideDown('cal');
	
	$('status').setStyle({
		'top': '5.5em',
		'left': width+5+'em',
		'right': '3em',
		'display': 'none'
		});
		
	
	getOptimisationOptions();
	
	updateFetch();
	new PeriodicalExecuter(updateFetch, 2);
	updateLoadable();
	new PeriodicalExecuter(updateLoadable, 10);
	updateSessions();
	new PeriodicalExecuter(updateSessions, 20);
	updateLock();
	new PeriodicalExecuter(updateLock, 20);
	
	$('course_select').onchange = function() {
		$('series_select').update("");
		$('number_select').update("");
		serverLoadCourse($F('course_select'), function(courses) {
			if (courses.include($F('course_select')))
			{
				new Ajax.Request('json/groups_in', {
					method: 'get',
					parameters: { course: $F('course_select') },
					onSuccess: function(transport) {
						var series = transport.responseJSON;
						var sel = $('series_select');
						sel.update('<option>[series]</option>');
						series.each(function(ser) {
							var e = $(document.createElement('option'));
							e.update(ser.series);
							e.value = ser.series;
							sel.appendChild(e);
							});
							
						$('number_select').update('<option>[group]</option>');
							
						sel.onchange = function() {
							var this_series = series.find(function(ser) {
								return ($F('series_select') == ser.series);
								});
							var groups = this_series.numbers;
							
							var sel = $('number_select');
							sel.update('<option>[group]</option>');
							groups.each(function(g) {
								var e = $(document.createElement('option'));
								e.update(g);
								e.value = g;
								sel.appendChild(e);
								});
							}
						}
					});
			}
			});
	}
	
	$('status').appear({ duration: 3 });
	Effect.SlideDown('main_toolbar');
	
	$('busy').fade();
}

// Locks the current session
function lock(key)
{
	$('busy').appear({ to: 0.85, duration: 0.1 });
	new Ajax.Request('session/lock', {
		method: 'post',
		parameters: { 'key': key, 'session': window.session_id },
		onSuccess: function(transport){
			_setLockLinks(transport.responseJSON.status);
			$('busy').fade();
			}
		});
}

// Tries to unlock the current session
function unlock(key)
{
	$('busy').appear({ to: 0.85, duration: 0.1 });
	new Ajax.Request('session/unlock', {
		method: 'post',
		parameters: { 'key': key, 'session': window.session_id },
		onSuccess: function(transport){
			var resp = transport.responseJSON;
			var st = resp.status;
			_setLockLinks(st);
			// if it's still locked after the request then we failed
			if (st == 'locked') alert('Invalid lock key.');
			$('busy').fade();
			}
		});
}

// Opens the optimisation UI
function optimise()
{
	if (window.is_optimising) {
	    alert("Already running another optimisation task!");
	} else {
	    var tbl = $('optim_opts');
        tbl.update("");
	    window.optim_collection.each( function(opt) {
            var row = $(document.createElement('tr'));
            
            var dcell = $(document.createElement('td'));
            dcell.update(opt.desc);
            row.appendChild(dcell);
            
            var ocell = $(document.createElement('td'));
            var o_txt = $(document.createElement('input'));
            o_txt.id = 'opt_' + opt.name;
            o_txt.type = 'text';
            o_txt.value = opt.normal;
            ocell.appendChild(o_txt);
            row.appendChild(ocell);
            
            tbl.appendChild(row);
        });
	    $('optim_starter').appear({ to: 0.9 });
	}
}

// Starts the optimisation process
function startOptimise()
{
    if (window.is_optimising) {
        // do nothing
    } else {
        
        window.is_optimising = true;
        $('busy').appear({ to: 0.85 });
        $('optim_starter').fade();
        // build the hash
        var config = {};
        window.optim_options.each( function(name) {
            config[name] = $F('opt_'+name);
        });
        var config_json = Object.toJSON(config);
        new Ajax.Request('session/optimise', {
    		method: 'post',
    		parameters: { 
    		    'session': window.session_id,
    		    'config': config_json
    		},
    		onSuccess: function(transport){
    			_optimiseChecker();
    			}
    		});
    		
		}
}

function reloadOptimised()
{
    $('busy').appear({ to: 0.85, duration: 0.1 });
    _optimCheck();
}

function _optimCheck()
{
    new Ajax.Request('session/optimise', {
		method: 'get',
		parameters: { 'session' : window.session_id },
		onSuccess: function(transport){
			var resp = transport.responseJSON;
			if (resp.status == 'error') {
			    $('busy').fade();
			    window.is_optimising = false;
			    alert("Optimisation terminated due to error.");
			} else if (resp.status == 'done') {
				// clear everything
				_clearEventsInternal();
				autoClear();
				
				// add the optimal tt
				resp.session.each( function(g) {
					_addGroupInternal(g.course, g.series, g.number, g.events);
				});
				
				// update fields
				$('optim_score').update(resp.fitness);
				$('optim_time').update(resp.time);
				
				// clear the trace
				var trc = $('optim_trace');
			    trc.update('');
			    // construct new trace table
			    resp.trace.each( function(titem) {
			        var row = $(document.createElement('tr'));
			        var cell = $(document.createElement('td'));
			        cell.update(titem.desc);
			        row.appendChild(cell);
			        cell = $(document.createElement('td'));
			        cell.update("-" + titem.penalty);
			        row.appendChild(cell);
			        
			        trc.appendChild(row); 
			    });
			
				
				$('busy').fade();
				
				$('optim_feedback').appear({ to: 0.9 });
				
				window.is_optimising = false;
			} else {
			    _optimiseChecker();
			}
		}
		});
}

// Internal function to check and retrieve optimal timetable
// TODO: UI for this!
function _optimiseChecker()
{
    window.is_optimising = true;
	new PeriodicalExecuter( function(pe) {
	    pe.stop();
		_optimCheck();
	},1);
}

function getOptimisationOptions()
{
    new Ajax.Request('json/optimise_options', {
        method: 'get',
        onSuccess: function(transport) {
            var opts = transport.responseJSON;
            var tbl = $('optim_opts');
            tbl.update("");
            window.optim_options = opts.pluck('name');
            window.optim_collection = opts;
            window.optim_collection.each( function(opt) {
                var row = $(document.createElement('tr'));
                
                var dcell = $(document.createElement('td'));
                dcell.update(opt.desc);
                row.appendChild(dcell);
                
                var ocell = $(document.createElement('td'));
                var o_txt = $(document.createElement('input'));
                o_txt.id = 'opt_' + opt.name;
                o_txt.type = 'text';
                o_txt.value = opt.normal;
                ocell.appendChild(o_txt);
                row.appendChild(ocell);
                
                tbl.appendChild(row);
            });
            
        }
    });
}

// Updates the lock status indicator and link
function updateLock()
{
	new Ajax.Request('session/lock', {
		method: 'get',
		parameters: { 'session': window.session_id },
		onSuccess: function(transport){
			var resp = transport.responseJSON;
			_setLockLinks(resp.status);
			}
		});
}

// Resets the lock links according to the lock status given
function _setLockLinks(status)
{
	if (status == 'locked')
	{
		$('lock').update("");
		var a = $(document.createElement('a'));
		a.href = '#';
		a.update('Unlock');
		a.onclick = function(){
			var key = prompt("Enter the lock key", "");
			if (key == '' || !key || key=='undefined') return false;
			unlock(key);
			return false;
			};
		$('lock').appendChild(a);
	} 
	else if (status == 'unlocked')
	{
		$('lock').update("");
		var a = $(document.createElement('a'));
		a.href = '#';
		a.update('Lock');
		a.onclick = function(){
			var key = prompt("Enter the lock key", "");
			if (key == '' || !key || key=='undefined') return false;
			lock(key);
			return false;
			};
		$('lock').appendChild(a);
	}
}

// Called to add a new course to the new automatic "Add courses" dialog
function autoCourseAdd()
{
	var course = prompt("Enter course code", "");
	if (course == '' || !course) return;
	// upcase it, just in case (haha, case)
	course = course.toUpperCase();
	
	$('busy').appear({ to: 0.85 });
	
	// check the course exists first
	new Ajax.Request('loadable', {
		method: 'get',
		onSuccess: function(transport) {
			var courses = transport.responseJSON;
			var course_names = courses.pluck('course');
			if (!course_names.include(course)){
				var yn = confirm('Could not find course! Do you want to attempt fetching from mySI-net?');
				if (yn) {
					startFetch(course);
					alert('Please wait for fetch to complete and try again.');
				}
				
				$('busy').fade();
				return;
				}
			// start the course opener
			autoOpenCourse(course);
			}
		});
}

// guesses at the courses on the timetable at current
// and passes them to autoOpenCourse
function autoGuess()
{
	if ($('auto_courses').childElements().length > 0) return;
	var cs = getLoadedCourses();
	cs.each(function(course) {
		autoOpenCourse(course);
	});
}

// clears all the courses
function autoClear()
{
	$('auto_courses').update(" ");
}

// the heart of the new "Add courses" dialog
function autoOpenCourse(course)
{
	$('busy').appear({ to: 0.85 });
	// frist, request an array of all the series and groups belonging to the course
	new Ajax.Request('json/groups_in', {
		method: 'get',
		parameters: { 'course': course },
		onSuccess: function(transport) {
			var series = transport.responseJSON;
			var tbl = $('auto_courses');
			
			// get ready to create a new table row in
			// the dialog
			var row = $(document.createElement('tr'));

			// fetch the list of already loaded series on the UI
			// (we use this later)
			var lser = getLoadedSeries();
			
			var cell = $(document.createElement('td'));
			cell.update(course);
			row.appendChild(cell);
			
			// for each of the series we've gotten from the server
			series.each(function (ser) {
				// if we have multiple options (more than one group) we add an option box
				// otherwise just put the single option in
				// so if we have for example just the "L" series and no "L1" or "L2"
				// then we simple add it rather than making them select from one
				if (ser.numbers.length > 1) {
					var cell = $(document.createElement('td'));
					var sel = $(document.createElement('select'));
					
					// assume we select the blank unless it's already present
					var cur_n = '?';
					
					// if this group is already on our timetable somewhere
					// just use the number already chosen! 
					var g = lser.get(course+'|'+ser.series);
					if (g) {
						cur_n = g;
					}
					
					// create the null option
					var nullopt = $(document.createElement('option'));
					nullopt.update(ser.series + '?');
					nullopt.value = '?';
					if (cur_n == '?') nullopt.selected = 'selected';
					sel.appendChild(nullopt);
					
					// create the options
					ser.numbers.each(function(n) {
						var opt = $(document.createElement('option'));
						opt.update(ser.series + n);
						if (n == cur_n) opt.selected = 'selected';
						opt.value = n;
						sel.appendChild(opt);
					});
					
					// change event handler to update the series we've chosen
					sel.onchange = function() {
					    if (cur_n == '?') {
					        // we've been on the blank option
					        if ($F(sel) == '?') {
					            // do nothing
					            cur_n = $F(sel);
					        } else {
					            // load the new group
					            cur_n = $F(sel);
					            loadGroupOnce(course, ser.series, cur_n);
					        }
					    } else if ($F(sel) == '?') {
					        // if we picked the blank option then just clear
					        // anything there.
					        clearEvent(course+'|'+ser.series+'|'+cur_n);
					        cur_n = $F(sel);
					    } else {
					        // not-blank option to another not-blank
			                clearEvent(course+'|'+ser.series+'|'+cur_n);
    						cur_n = $F(sel);
    						loadGroupOnce(course, ser.series, cur_n);
					    }
					};
					
					cell.appendChild(sel);
					row.appendChild(cell);
				} else {
					// only one option, so add it if we have to
					// and just show it on the dialog
					var cell = $(document.createElement('td'));
					cell.update(ser.series);
					row.appendChild(cell);
					var g = lser.get(course+'|'+ser.series);
					if (!g) loadGroupOnce(course, ser.series, ser.numbers[0]);
				}
			});
				
			tbl.appendChild(row);
			
			// and we're done!
			
			$('busy').fade();
			}
		});
}

// called at startup
function startSession()
{
	$('busy').appear({ to: 0.85, duration: 0.1 });
	newSession(function() {
		init();
		});
}

// Requests a new user session UUID from the server
// and resets the UI to reflect the new session
function newSession(then)
{
	$('busy').appear({ to: 0.85, duration: 0.1 });
	new Ajax.Request('session/new', {
		method: 'post',
		onSuccess: function(transport) {
			var resp = transport.responseJSON;

			_clearEventsInternal();
			autoClear();

			window.session_id = resp.session;
			
			// update the session div
			var sessdiv = $('session_details');
			sessdiv.update('');

			// add the save button
			var a = $(document.createElement('a'));
			a.href = '#';
			a.update('Save');
			a.onclick = function() {
				var user = prompt('Enter your name', "");
				if (user == '' || user == 'undefined' || !user) return false;
				var desc = prompt('Enter a description for the timetable', "");
				new Ajax.Request('session/name', {
					method: 'post',
					parameters: { 'session': resp.session, 'user': user, 'desc': desc },
					onSuccess: function(transport) {
						$('session_details').update("Saved <a href='session/ical?session="+resp.session+"&seq="+Math.ceil(Math.random()*100000) +"'>iCal permalink</a>");
						updateSessions();
						}
					});
				return false;
				};
			sessdiv.appendChild(a);
			$('busy').fade();
			updateLock();
			then();
			}
		});
}

function loadSession(id)
{
    window.location.hash = "#session=" + id;
    _loadSession(id);
}

// Loads an existing user session
// we're assuming here that it exists
function _loadSession(id)
{
	$('busy').appear({ to: 0.85, duration: 0.1 });
	if (id == '' || id == '[session]'){ alert('Invalid session id'); return; }
	_clearEventsInternal();
	autoClear();
	window.session_id = id;
	updateLock();
	var sessdiv = $('session_details');
	sessdiv.update("Loaded. <a href='session/ical?session="+id+"&seq="+Math.ceil(Math.random()*100000) +"'>iCal permalink</a>");
	// request a JSON of all the events belonging to the session
	// so we can just bulk-add them
	new Ajax.Request('session/json', {
		method: 'get',
		parameters: {
			'session': window.session_id
			},
		onSuccess: function(transport) {
			var groups = transport.responseJSON;
			groups.each(function(g) {
				_addGroupInternal(g.course, g.series, g.number, g.events);
				});
			$('busy').fade();
			}
		});
}

// Colour handling. Kinda hacky, we just have an array and a global index
// and increment it each time we create something that wants a colour
function getColour()
{
	var colours = [ 'rgb(255,204,102)', 'rgb(204,255,102)', 'rgb(102,255,204)', 'rgb(102,204,255)', 'rgb(204,102,255)', 'rgb(255,111,207)', 'rgb(255,102,102)', 'rgb(255,255,102)', 'rgb(102,255,102)', 'rgb(102,102,255)', 'rgb(255,102,255)' ];
	return colours[window.colourIdx];
}

function nextColour()
{
	window.colourIdx++;
	if (window.colourIdx > 10) window.colourIdx = 0;
	return getColour();
}

// Was used to add log messages
// now not used so much
function log(msg)
{
	var p = $(document.createElement('p'));
	p.update(msg);
	var msgs = $('messages');
	msgs.appendChild(p);
	msgs.scrollTo(p);
}

// Updates the sessions selector
function updateSessions()
{
	new Ajax.Request('session/all', {
		method: 'get',
		onSuccess: function(transport) {
			var sel = $('session_select');
			var arr = transport.responseJSON;
			
			var oldsel = $F('session_select');
			sel.update("");
			arr.each(function(sess) {
				var e = $(document.createElement('option'));
				e.value = sess.id;
				e.update(sess.user + ": " + sess.desc);
				if (sess.id == oldsel) e.selected = 'selected';
				sel.appendChild(e);
				});
			}
		});
}

// Updates the "threads" display on the info sidebar
function updateFetch()
{
	new Ajax.Request('fetch', {
		method: 'post',
		onSuccess: function(transport) {
			var div=$('fetch');
			var threads = transport.responseJSON;
			
			div.update("Threads");
			var lst = $(document.createElement('ul'));
			
			threads.each(function(thread) {
				var e = $(document.createElement('li'));
				e.update(thread.desc + ": " + thread.status);
				lst.appendChild(e);
				});
			div.appendChild(lst);
			}
		});
}

// Instructs the server to begin fetching a course from mySI-net
function startFetch(course)
{
	if (course == '' || course == 'undefined' || !course) return;
	new Ajax.Request('fetch', {
		method: 'post',
		parameters: { 'course': course }
		});
}

// Updates the course index from the server
function updateLoadable()
{
	new Ajax.Request('loadable', {
		method: 'get',
		onSuccess: function(transport) {
			var div=$('loadable');
			var courses = transport.responseJSON;
			//alert(courses);
			
			var sel = $('course_select');
			var oldsel = $F('course_select');
			sel.update('<option>[course]</option>');
			
			div.update("Course Index");
			
			var lst = $(document.createElement('ul'));
			courses.each(function(obj) {
				var course = obj.course;
				var e = $(document.createElement('li'));
				e.update(course + ' ('+obj.updated+')');
				lst.appendChild(e);
				var e = $(document.createElement('option'));
				e.value = course;
				if (course == oldsel) e.selected = 'selected';
				e.update(course);
				sel.appendChild(e);
				});
			div.appendChild(lst);
			}
		});
}

// These are both kinda unnecessary nowadays, since all courses
// we add are already loaded by default (the server now greedy-loads
// courses into memory rather than being lazy like it used to)

// This makes sure a course is in memory on the server before it adds it
function loadCourseThenGroup(course, series, number)
{
	serverLoadCourse(course, function(){
		loadGroup(course, series, number);
		});
}

// Instructs the server to fetch a course into memory from disk
function serverLoadCourse(course, then)
{
	$('busy').appear({ to: 0.85, duration: 0.1 });
	new Ajax.Request('load', {
		method: 'post',
		parameters: {
			'course': course
			},
		onSuccess: function(transport) {
			var courses = transport.responseJSON;
			then(courses);
			$('busy').fade();
			}
		});
}

// Loads a group, but only once!
function loadGroupOnce(course, series, number)
{
	$('busy').appear({ to: 0.85, duration: 0.1 });
	if (course == '[course]' || series == '[series]' || number == '[group]') alert('Please select all fields.');
	else if (window.items.get(course+'|'+series+'|'+number)) alert('Already loaded.');
	else loadGroup(course, series,number);
	$('busy').fade();
}

// Loads a group from the server and adds it to the display
function loadGroup(course, series, number)
{
	new Ajax.Request('session/add', {
		method: 'post',
		parameters: {
			'session': window.session_id, 
			'course': course, 'series': series, 'number': number
			},
		onSuccess: function(transport) {
			var arr = transport.responseJSON;
			_addGroupInternal(course, series, number, arr);
			}
		});
}

// Internal function for adding a group
function _addGroupInternal(course, series, number, arr)
{
	nextColour();
			
	// load actual events onto the calendar
	arr.each(function(item) {
		loadEvent(course+'|'+series+'|'+number, item);
		});
	
	// put it in the series legend
	var e = $(document.createElement('li'));
	e.setStyle( {'background': getColour(), 'display': 'none' });
	e.update("");
	var a = $(document.createElement('a'));
	a.href = '#';
	a.update('X');
	a.onclick = function() {
		clearEvent(course+'|'+series+'|'+number);
		return false;
		};
	e.appendChild(a);
	e.insert(course + " " + series + number + "&nbsp;");

	// this is purefly for the pretty animation
	// we wait a second so that we don't make listings appear
	// while the old ones are still disappearing!
	new PeriodicalExecuter(function(pe) {
		$('loaded').appendChild(e);
		e.appear();
		pe.stop();
		},1);
	
	
	var id = course+'|'+series+'|'+number;
	// check the items array exists first (might not if it's an empty series)
	if (!window.items.get(id)) window.items.set(id, new Array());
	// add the legend element to the array!
	var arra = window.items.get(id);
	arra.push(e);

}

// Fetches an array of loaded groups
function getLoadedGroups()
{
	var arr = new Array();
	window.items.each(function (i) {
		var key = i[0];
		var key_exp = new Array();
		key.scan(/[A-Za-z0-9]+/, function(item) { key_exp.push(item); });
		arr.push(key_exp[0]);
	});
	return arr;
}

// Fetches a hash of course|series => number reflecting what's loaded on the UI
function getLoadedSeries()
{
	var h = new Hash();
	window.items.each(function (i) {
		var key = i[0];
		var key_exp = new Array();
		key.scan(/[A-Za-z0-9]+/, function(item) { key_exp.push(item); });
		if (!key_exp[2]) key_exp.push('0');
		h.set(key_exp[0]+'|'+key_exp[1], key_exp[2]);
	});
	return h;
}

// Gets a list of courses that have at least one series loaded
function getLoadedCourses()
{
	var arr = new Hash();
	window.items.each(function (i) {
		var key = i[0];
		var key_exp = new Array();
		key.scan(/[A-Za-z0-9]+/, function(item) { key_exp.push(item); });
		arr.set(key_exp[0], key_exp[1]);
	});
	var keys = arr.keys();
	return keys;
}

// Requests an event to be removed from the session
// and then destroys it on the UI
function clearEvent(id)
{
    var width = window.calWidth;
    
	var id_exp = [];
	id.scan(/[A-Za-z0-9]+/, function(item) { id_exp.push(item); });
	
	var event = null;
	// find us in the window.evts list
	window.evts.each(function (evthash) {
	    if (evthash.id == id) {
	        event = evthash;
	    }
	});
	
	if (event != null) {
	    // restore any events that used to be next to us
	    window.evts.each(function (evthash) {
    	    if ((evthash.wday == event.wday) && (
    	            (evthash.start >= event.start && evthash.start <= event.finish) ||
    	            (evthash.finish >= event.start && evthash.finish <= event.finish))
    	            ) {
    	        evthash.cal_elem.setStyle({
    	            'width': ((width-3)/7)+'em',
    	            'left': 3 + evthash.wday*((width-3)/7)+'em'
    	        });
                evthash.cal_elem.update(evthash.text);
    	    }
    	});
	}
	
	// remove us from the window.evts list
	window.evts = window.evts.without(event);
	
	// initiate the removal request, only update UI once it returns
	new Ajax.Request('session/remove', {
		method: 'post',
		parameters: {
			'session': window.session_id,
			'course': id_exp[0], 'series': id_exp[1], 'number': id_exp[2]
			},
		onSuccess: function(transport) {
			var it = window.items.unset(id);
			it.each(function(e) {
				e.fade();
				// we can't use Element.remove until after the animation's done!
				new PeriodicalExecuter(function(pe) {
					Element.remove(e);
					pe.stop();
					},1);
				});
			}
	});
}

// clears the entire session
function clearEvents()
{
	new Ajax.Request('session/clear', {
		method: 'post',
		parameters: { 'session': window.session_id },
		onSuccess: function(transport) {
			_clearEventsInternal();
			autoClear();
			}
		});
}

function _clearEventsInternal()
{
	var it = window.items.clone();
	it.each(function(i) {
	key = i[0]; es = i[1];
	
	es.each(function(e) {
		e.fade();
		// we can't use Element.remove until after the animation's done!
		new PeriodicalExecuter(function(pe) {
					Element.remove(e);
					pe.stop();
					},1);
		});
	window.items.unset(key);
	window.evts = new Array();
	});
}

// Adds an event to the calendar display
function loadEvent(id, event)
{
	var cal = window.cal;
	var width = window.calWidth;
	var height = window.calHeight;
	
	event['start'] -= 7.0;
	event['finish'] -= 7.0;
	
	var makehalf = false;
	
	// check for other events that run at the same time
	window.evts.each(function (evthash) {
	    if ((evthash.wday == event.wday) && (
	            (evthash.start >= event.start && evthash.start <= event.finish) ||
	            (evthash.finish >= event.start && evthash.finish <= event.finish))
	            ) {
	        evthash.cal_elem.setStyle({
	            'width': ((width-3)/14)+'em'
	        });
            evthash.cal_elem.update("<span>"+evthash.name+"</span>");
	        makehalf = true;
	    }
	});
	
	if (!window.items.get(id)) window.items.set(id,new Array());
	var arr = window.items.get(id);
	
	var e = $(document.createElement('div'));
	arr.push(e);
	window.evts.push( { 
	    start: event.start, 
	    finish: event.finish, 
	    wday: event.wday, 
	    cal_elem: e, 
	    name: event.name,
	    text: event.text,
	    'id': id
	} );
	e.addClassName('event');
	e.setStyle( {
		'left': 3 + event['wday']*((width-3)/7)+'em',
		'width': ((width-3)/7)+'em',
		'top': 2 + event['start']*((height-2)/12)+'em',
		'height': (2 + event['finish']*((height-2)/12)) - (2 + event['start']*((height-2)/12))+'em',
		'background': getColour(),
		'display': 'none'
		});
	if (makehalf) {
	    e.setStyle( {
	        'width': ((width-3)/14)+'em',
	        'left': 3 + (event['wday']+0.5)*((width-3)/7)+'em'
	    });
	}
	
	if (!makehalf) {
	    e.update(event.text);
	} else {
	    var sp = $(document.createElement('span'));
	    e.update('');
	    sp.update(event.name);
	    e.appendChild(sp);
	}
	
	e.observe('click', function(evt) {
	    var tp = $('tooltip');
	    tp.style.left = Event.pointerX(evt)+"px";
	    tp.style.top = Event.pointerY(evt)+"px";
	    
	    if (!tp.visible()) {
	        tp.update(event['location']);
    	    var odv = $(document.createElement('div'));
    	    odv.addClassName('outer');
    	    odv.setStyle( { 'width': (event.date_series.size()*30)+'px' } );
    	    event.date_series.each(function (ev) {
    	        var dv = $(document.createElement('div'));
    	        if (ev.taught == true) {
    	            dv.addClassName('taught');
    	        } else {
    	            dv.addClassName('nottaught');
    	        }
    	        dv.update(ev.date);
    	        odv.appendChild(dv);
    	    });
    	    tp.appendChild(odv);
    	    tp.show();
	    }
	});
	e.observe('mousemove', function(evt) {
	    var tp = $('tooltip');
	    tp.hide();
	});
	/*e.observe('mousemove', function(evt) {
	    var tp = $('tooltip');
	    tp.style.left = Event.pointerX(evt)+"px";
	    tp.style.top = Event.pointerY(evt)+"px";
	    tp.show();
	});*/
	/*e.observe('mouseout', function(evt) {
	   var tp = $('tooltip');
	   tp.hide(); 
	});*/
	cal.appendChild(e);
	e.appear({ to: 0.85 });
}
